package com.zhongkun.datahome.common.utils.bean;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.beans.BeanCopier;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Bean属性操作工具类（支持属性拷贝、集合转换、Map映射等操作）
 */
public class BeanConverter {
    private static final Logger log = LoggerFactory.getLogger(BeanConverter.class);
    
    // 缓存BeanCopier实例提升性能
    private static final Map<String, BeanCopier> BEAN_COPIER_CACHE = new ConcurrentHashMap<>();

    /**
     * 合并多个源对象的属性到目标对象
     * @param targetType 目标对象类型
     * @param sources 多个源对象（支持不同源对象属性合并）
     * @return 合并后的目标对象实例
     * @throws RuntimeException 当对象创建失败时抛出
     */
    public static <T> T copyBean(Class<T> targetType, Object... sources) {
        if (sources == null || sources.length == 0) return null;
        
        try {
            T target = targetType.getDeclaredConstructor().newInstance();
            for (Object source : sources) {
                if (source == null) continue;
                
                String cacheKey = source.getClass().getName() + targetType.getName();
                BeanCopier copier = BEAN_COPIER_CACHE.computeIfAbsent(cacheKey, 
                    k -> BeanCopier.create(source.getClass(), targetType, false));
                copier.copy(source, target, null);
            }
            return target;
        } catch (Exception e) {
            log.error("Bean copy failed. Target: {}, Sources: {}", targetType, Arrays.toString(sources), e);
            throw new RuntimeException("Bean copy failure", e);
        }
    }

    /**
     * 转换对象列表到指定类型列表
     * @param sourceList 源对象列表
     * @param targetType 目标对象类型
     * @return 转换后的目标对象列表（空列表返回空集合）
     * @throws RuntimeException 当对象创建失败时抛出
     */
    public static <T> List<T> convertList(List<?> sourceList, Class<T> targetType) {
        if (CollectionUtils.isEmpty(sourceList)) return Collections.emptyList();
        
        List<T> result = new ArrayList<>(sourceList.size());
        try {
            Object firstItem = sourceList.get(0);
            BeanCopier copier = BeanCopier.create(firstItem.getClass(), targetType, false);
            
            for (Object source : sourceList) {
                T target = targetType.getDeclaredConstructor().newInstance();
                copier.copy(source, target, null);
                result.add(target);
            }
            return result;
        } catch (Exception e) {
            log.error("List conversion failed. Target type: {}", targetType, e);
            throw new RuntimeException("List conversion failure", e);
        }
    }

    /**
     * 将对象列表转换为指定字段值为Key的Map
     * @param list 源对象列表
     * @param fieldName 作为Key的字段名称
     * @return 字段值到对象的映射表（空列表返回空Map）
     * @throws IllegalArgumentException 当字段不存在或访问失败时抛出
     */
    public static <K, V> Map<K, V> toMapByField(List<V> list, String fieldName) {
        if (CollectionUtils.isEmpty(list) || StringUtils.isBlank(fieldName)) {
            return Collections.emptyMap();
        }
        
        Map<K, V> map = new HashMap<>(list.size());
        try {
            Field keyField = getAccessibleField(list.get(0).getClass(), fieldName);
            
            for (V item : list) {
                @SuppressWarnings("unchecked")
                K key = (K) keyField.get(item);
                map.put(key, item);
            }
            return map;
        } catch (Exception e) {
            log.error("Convert list to map failed. Field: {}", fieldName, e);
            throw new IllegalArgumentException("Field access failure", e);
        }
    }

    /**
     * 获取可访问的字段对象（带缓存）
     */
    private static Field getAccessibleField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        return field;
    }
}